Chapter 5: Object/relational mapping XML (classic)
Object relational mapping allows you to declare in which tables and columns of your database the component data will be stored.
If ORM is familiar to you: The OpenXava mapping is used to generate the code and XML files needed to object/relational mapping. Actually the code is generated for:
Hibernate 3.x.
EntityBeans CMP 2 of JBoss 3.2.x y 4.0.x.
EntityBeans CMP 2 of Websphere 5, 5.1y 6.
If Object/relational tools are not familiar to you: Object/relational tools allow you to work with objects instead of tables and columns, and to generate automatically the SQL code to read and update the database.
OpenXava generates a set of Java classes that represent the model layer of your application (the business concepts with its data and its behavior). You can work directly with these objects, and you do not need direct access to the SQL database. Of course you have to define precisely how to map your classes to your tables, and this work is done in the mapping part.
Easier impossible.
You see how the table name is qualified (with collection/schema name included). Also you see that the separator is @separator@ instead of a dot (.), this is useful because you can define the separator value in your build.xml and thus the same application can run against databases with or without support for collections or schemes.
property (required): Name of a property defined in the model part.
column (optional since v3.1.4): Name of a table column. If omitted the property name is assumed as column name.
cmp-type (optional): Java type of the attribute used internally in your object to store the property value. This allows you to use Java types more closer to the database, without contaminating your Java model. It is used with a converter.
formula (optional): (New in v3.1.4) To calculate the value of the property using the database. It must be a valid SQL fragment. If you use formula you can omit the column attribute. It does not work with EntityBeans CMP2.
converter (one, optional): Implements your custom logic to convert from Java to DB format and vice versa.
For now, you have seen simple examples for mapping a property to a column. A more advanced case is using a converter. A converter is used when the Java type and the DB type don't match, in this case a converter is a good idea. For example, imagine that in database the zip code is VARCHAR while in Java you want to use an int. A Java int is not directly assignable to a VARCHAR column in database, but you can use a converter to transform that int to String. Let's see it:
cmp-type indicates to which type the converter has to convert to and it is the type of the internal attribute in the generated class code. It must be a type close (assignable directly from JDBC) to the column type in database.
The converter code is:
packageorg.openxava.converters;/**
* In java an int and in database a String.
*
* @author Javier Paniza
*/publicclass IntegerStringConverter implements IConverter {// 1privatefinalstaticInteger ZERO = newInteger(0);publicObject toDB(Object o)throws ConversionException {// 2return o==null?"0":o.toString();}publicObject toJava(Object o)throws ConversionException {// 3if(o == null)return ZERO;if(!(o instanceofString)){thrownew ConversionException("conversion_java_string_expected");}try{returnnewInteger((String) o);}catch(Exception ex){
ex.printStackTrace();thrownew ConversionException("conversion_error");}}}
A converter must implement IConverter (1), this forces it to have a toDB() (2) method, that receives the object of the type used in Java (in this case an Integer) and returns its representation using a type closer to the database (in this case String hence assignable to a VARCHAR column). The toJava() method has the opposite goal; it gets the object in database format and it must return an object of the type used in Java.
If there are any problem you can throw a ConversionException.
You see that this converter is in org.openxava.converters, i. e., it is a generic converter that comes with the OpenXava distribution. Another quite useful generic converter is ValidValuesLetterConverter. That one allows to map properties of type valid-values. For example, if you have a property like this:
valid-values generates a Java property of type int in which 0 is used to indicate the empty value, 1 is 'local', 2 is 'national' and 3 is 'international'. But what happens, if in the database a single letter ('L', 'N' or 'I') is stored? In this case you can use a mapping like this:
As you put 'LNI' as a value to letters, the converter matches the 'L' to 1, the 'N' to 2 and the 'I' to 3. You also see how converters are configurable using its properties and this makes the converters more reusable (as calculators, validators, etc).
Using formula (new in v3.1.4) attribute you can define a calculation for your property. This calculation is expressed using SQL, and it is executed by the database, not by Java. You only need to write a valid SQL fragment:
The use is simple. Put your calculation exactly in the same way that you would put it in a SQL statement.
The properties with formula are read only properties. When the object is read from database the calculation is done by the database and the property is populate with it.
This is an alternative to calculated properties. It has the advantage that the user can filter by this property in list mode, and the disadvantage that you have to use SQL instead of Java, and you have no live recalculation of the value.
reference-mapping-detail (several, required): Maps a table column to a property of the reference key. If the key of the referenced object is multiple, then you will have several reference-mapping-detail.
Making a reference mapping is easy. For example, if you have a reference like this:
INVOICE_YEAR and INVOICE_NUMBER are columns of the DELIVERY table that allows accessing to its invoice, that is it's the foreign key although declaring it as a foreign key in database is not required. You must map this columns to the key properties in Invoice, like this:
As you see, to indicate the properties of referenced models you can qualify them.
Also it's possible to use converters in a reference mapping:
<reference-mappingreference="drivingLicence"><reference-mapping-detailcolumn="DRIVINGLICENCE_TYPE"referenced-model-property="type"cmp-type="String"><!-- 1 In this case this line can be omitted --><converterclass="org.openxava.converters.NotNullStringConverter"/><!-- 2 --></reference-mapping-detail><reference-mapping-detailcolumn="DRIVINGLICENCE_LEVEL"referenced-model-property="level"/></reference-mapping>
You can use the converter just like in a simple property (2). The difference in the reference case is that if you do not define a converter, then a default converter is not used. This is because applying in an indiscriminate way converters on keys can produce problems in some circumstances. You can use cmp-type (1) (new in v2.0) to indicate the Java type of the attribute used internally in your object to store the value. This allows you to use Java types closer to the database; cmp-type is not needed if the database type is compatible with Java type.
Multiple property mapping
With <multiple-property-mapping/> you can map several table columns to a single Java property. This is useful if you have properties of custom class that have itself several attributes to store. Also it is used when you have to deal with legate database schemes.
The syntax for this type of mapping is:
converter (one, required): Implements the logic to convert from Java to the database and vice versa. Must implement IMultipleConverter.
cmp-field (several, required): Maps each column in the database with a property of a converter.
A typical example is the generic converter Date3Converter, that allows to store in the database 3 columns and in Java a single property of type java.util.Date.
DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that store the delivery date, and day, month and year are properties of Date3Converter. And here Date3Converter:
packageorg.openxava.converters;importjava.util.*;importorg.openxava.util.*;/**
* In java a <tt>java.util.Date</tt> and in database 3 columns of
* integer type. <p>
*
* @author Javier Paniza
*/publicclass Date3Converter implements IMultipleConverter {<!-- 1 -->privateint day;privateint month;privateint year;publicObject toJava()throws ConversionException {<!-- 2 -->return Dates.create(day, month, year);}publicvoid toDB(Object javaObject)throws ConversionException {<!-- 3 -->if(javaObject == null){
setDay(0);
setMonth(0);
setYear(0);return;}if(!( javaObject instanceof java.util.Date)){thrownew ConversionException("conversion_db_utildate_expected");}
java.util.Date date = (java.util.Date) javaObject;Calendar cal = Calendar.getInstance();
cal.setTime(date);
setDay(cal.get(Calendar.DAY_OF_MONTH));
setMonth(cal.get(Calendar.MONTH) + 1);
setYear(cal.get(Calendar.YEAR));}publicint getYear(){return year;}publicint getDay(){return day;}publicint getMonth(){return month;}publicvoid setYear(int i){
year = i;}publicvoid setDay(int i){
day = i;}publicvoid setMonth(int i){
month = i;}}
This converter must implement IMultipleConverter (1). This forces it to have a toJava() (2) method that must return a Java object from its property values (in this case year, month and day). The returned object is the mapped property (in this case deliveryDate). The calculator must have the method toDB() (3) too; this method receives the value of the property (a delivery date) and has to split it and to put the result in the converter properties (year, month and day).
Reference to aggregate mapping
A reference to an aggregate contains data that in the relational model are stored in the same table as the main entity. For example, if you have an aggregate Address associated to a Customer, the address data is stored in the same data table as the customer data. How can you map this case with OpenXava?
Let's see. In the model you can have:
You see how the aggregate members are mapped within the entity mapping that contains it. The only thing that you have to do is using the name of the reference as a prefix with an underline (in this case address_). You can observe that in the case of aggregates you can map references, properties and that you can use converters in the usual way.
Aggregate used in collection mapping
In case that you have a collection of aggregates, for example the invoice details, obviously the detail data is stored in a different table as the heading data. In this case the aggregate must have its own mapping. Let's see the example:
Here the model part of Invoice:
The aggregate mapping must be below of the main entity mapping. A component must have as many aggregate mappings as aggregates used in collections. The aggregate mapping has the same possibilities than entity mapping, with the exception that it's required to map a reference to the container object although maybe this reference is not defined in model. That is, although you do not define a reference to Invoice in InvoiceDetail OpenXava adds it automatically and you must map it (1).
Converters by default
You have seen how to declare a converter in a property mapping. But what happens, if you do not declare a converter? In reality in OpenXava all properties (except the key properties) have a converter by default. The default converters are defined in OpenXava/xava/default-converters.xml, that has a content like this:
<?xml version = "1.0" encoding = "ISO-8859-1"?><!DOCTYPE converters SYSTEM "dtds/converters.dtd"><!--In your project use the name 'converters.xml' or 'conversores.xml'--><converters><for-typetype="java.lang.String"converter-class="org.openxava.converters.TrimStringConverter"cmp-type="java.lang.String"/><for-typetype="int"converter-class="org.openxava.converters.IntegerNumberConverter"cmp-type="java.lang.Integer"/><for-typetype="java.lang.Integer"converter-class="org.openxava.converters.IntegerNumberConverter"cmp-type="java.lang.Integer"/><for-typetype="boolean"converter-class="org.openxava.converters.Boolean01Converter"cmp-type="java.lang.Integer"/><for-typetype="java.lang.Boolean"converter-class="org.openxava.converters.Boolean01Converter"cmp-type="java.lang.Integer"/><for-typetype="long"converter-class="org.openxava.converters.LongNumberConverter"cmp-type="java.lang.Long"/><for-typetype="java.lang.Long"converter-class="org.openxava.converters.LongNumberConverter"cmp-type="java.lang.Long"/><for-typetype="java.math.BigDecimal"converter-class="org.openxava.converters.BigDecimalNumberConverter"cmp-type="java.math.BigDecimal"/><for-typetype="java.util.Date"converter-class="org.openxava.converters.DateUtilSQLConverter"cmp-type="java.sql.Date"/></converters>
If you use a property of a type that is not defined here, by default OpenXava will assign the converter NoConversionConverter, a silly converter that don't perform anything.
In the case of key properties and references no converter are assigned; applying a converter to key properties can be problematic in certain circumstances, but even if you want to perform a conversion you can declare a converter explicitly in your mapping.
If you wish to modify the behavior of default converters in your application, you do not modify the OpenXava file, but you create your own converters.xml file in the folder xava of your project. You can assign a converter by default to a stereotype (using <for-stereotype/>).
Default mapping (new in v2.1.3)
Since version 2.1.3 OpenXava allows to define components without mapping, and it assumed a default mapping for it. For example, you can write:
<?xmlversion="1.0"encoding="ISO-8859-1"?><!DOCTYPE component SYSTEM "dtds/component.dtd"><componentname="Pupil"><entity><propertyname="number"type="int"key="true"size="2"required="true"/><propertyname="name"type="String"size="40"required="true"/><referencename="teacher"/></entity></component>
This component is mapped to the table Pupil, and the properties number and name are mapped to the columns number and name. The reference teacher is mapped to a column named teacher_number (if the key property of Teacher is named number).
Object/relational philosophy
OpenXava has been born and has been developed in an environment when it was necessary to work with legacy database without changing its structure. The result is that OpenXava:
Provides great flexibility when mapping with legacy database.
Does not provide some features natural for OOT and that requires to change database scheme, as inheritance support or polymorphic queries.
Another cool feature of OpenXava mapping is that applications are 100% portables from JBoss CMP2 to Websphere CMP2 without writing a single line of code. Furthermore, the portability between Hibernate, JPA and EJB2 version of an application is very high, the mapping and all automatic controllers are 100% portable, obviously the custom EJB2, JPA or Hibernate code is not so portable.
Table of Contents
Chapter 5: Object/relational mapping XML (classic)
Object relational mapping allows you to declare in which tables and columns of your database the component data will be stored.If ORM is familiar to you: The OpenXava mapping is used to generate the code and XML files needed to object/relational mapping. Actually the code is generated for:
- Hibernate 3.x.
- EntityBeans CMP 2 of JBoss 3.2.x y 4.0.x.
- EntityBeans CMP 2 of Websphere 5, 5.1y 6.
If Object/relational tools are not familiar to you: Object/relational tools allow you to work with objects instead of tables and columns, and to generate automatically the SQL code to read and update the database.OpenXava generates a set of Java classes that represent the model layer of your application (the business concepts with its data and its behavior). You can work directly with these objects, and you do not need direct access to the SQL database. Of course you have to define precisely how to map your classes to your tables, and this work is done in the mapping part.
Entity mapping
The syntax to map the main entity is:- table (required): Maps this table to the main entity of component.
- property-mapping (several, optional): Maps a property to a column in the database table.
- reference-mapping (several, optional): Maps a reference to one or more columns in the database table.
- multiple-property-mapping (several, optional): Maps a property to several columns in database table.
A plain example can be:Easier impossible.
You see how the table name is qualified (with collection/schema name included). Also you see that the separator is @separator@ instead of a dot (.), this is useful because you can define the separator value in your build.xml and thus the same application can run against databases with or without support for collections or schemes.
Property mapping
The syntax to map a property is:- property (required): Name of a property defined in the model part.
- column (optional since v3.1.4): Name of a table column. If omitted the property name is assumed as column name.
- cmp-type (optional): Java type of the attribute used internally in your object to store the property value. This allows you to use Java types more closer to the database, without contaminating your Java model. It is used with a converter.
- formula (optional): (New in v3.1.4) To calculate the value of the property using the database. It must be a valid SQL fragment. If you use formula you can omit the column attribute. It does not work with EntityBeans CMP2.
- converter (one, optional): Implements your custom logic to convert from Java to DB format and vice versa.
For now, you have seen simple examples for mapping a property to a column. A more advanced case is using a converter. A converter is used when the Java type and the DB type don't match, in this case a converter is a good idea. For example, imagine that in database the zip code is VARCHAR while in Java you want to use an int. A Java int is not directly assignable to a VARCHAR column in database, but you can use a converter to transform that int to String. Let's see it:cmp-type indicates to which type the converter has to convert to and it is the type of the internal attribute in the generated class code. It must be a type close (assignable directly from JDBC) to the column type in database.
The converter code is:
A converter must implement IConverter (1), this forces it to have a toDB() (2) method, that receives the object of the type used in Java (in this case an Integer) and returns its representation using a type closer to the database (in this case String hence assignable to a VARCHAR column). The toJava() method has the opposite goal; it gets the object in database format and it must return an object of the type used in Java.
If there are any problem you can throw a ConversionException.
You see that this converter is in org.openxava.converters, i. e., it is a generic converter that comes with the OpenXava distribution. Another quite useful generic converter is ValidValuesLetterConverter. That one allows to map properties of type valid-values. For example, if you have a property like this:
valid-values generates a Java property of type int in which 0 is used to indicate the empty value, 1 is 'local', 2 is 'national' and 3 is 'international'. But what happens, if in the database a single letter ('L', 'N' or 'I') is stored? In this case you can use a mapping like this:
As you put 'LNI' as a value to letters, the converter matches the 'L' to 1, the 'N' to 2 and the 'I' to 3. You also see how converters are configurable using its properties and this makes the converters more reusable (as calculators, validators, etc).
Using formula (new in v3.1.4) attribute you can define a calculation for your property. This calculation is expressed using SQL, and it is executed by the database, not by Java. You only need to write a valid SQL fragment:
<property-mapping property="unitPriceWithTax" formula="UNITPRICE * 1.16"/>The use is simple. Put your calculation exactly in the same way that you would put it in a SQL statement.The properties with formula are read only properties. When the object is read from database the calculation is done by the database and the property is populate with it.
This is an alternative to calculated properties. It has the advantage that the user can filter by this property in list mode, and the disadvantage that you have to use SQL instead of Java, and you have no live recalculation of the value.
Reference mapping
The syntax to map a reference is:- reference (required): The reference to map.
- reference-mapping-detail (several, required): Maps a table column to a property of the reference key. If the key of the referenced object is multiple, then you will have several reference-mapping-detail.
Making a reference mapping is easy. For example, if you have a reference like this:You can map it this way:
INVOICE_YEAR and INVOICE_NUMBER are columns of the DELIVERY table that allows accessing to its invoice, that is it's the foreign key although declaring it as a foreign key in database is not required. You must map this columns to the key properties in Invoice, like this:
If you have a reference to a model which key itself includes references, you can define it in this way:
As you see, to indicate the properties of referenced models you can qualify them.
Also it's possible to use converters in a reference mapping:
You can use the converter just like in a simple property (2). The difference in the reference case is that if you do not define a converter, then a default converter is not used. This is because applying in an indiscriminate way converters on keys can produce problems in some circumstances. You can use cmp-type (1) (new in v2.0) to indicate the Java type of the attribute used internally in your object to store the value. This allows you to use Java types closer to the database; cmp-type is not needed if the database type is compatible with Java type.
Multiple property mapping
With <multiple-property-mapping/> you can map several table columns to a single Java property. This is useful if you have properties of custom class that have itself several attributes to store. Also it is used when you have to deal with legate database schemes.The syntax for this type of mapping is:
- property (required): Name of the property to map.
- converter (one, required): Implements the logic to convert from Java to the database and vice versa. Must implement IMultipleConverter.
- cmp-field (several, required): Maps each column in the database with a property of a converter.
A typical example is the generic converter Date3Converter, that allows to store in the database 3 columns and in Java a single property of type java.util.Date.DAYDELIVERY, MONTHDELIVERY and YEARDELIVERY are 3 columns in database that store the delivery date, and day, month and year are properties of Date3Converter. And here Date3Converter:
This converter must implement IMultipleConverter (1). This forces it to have a toJava() (2) method that must return a Java object from its property values (in this case year, month and day). The returned object is the mapped property (in this case deliveryDate). The calculator must have the method toDB() (3) too; this method receives the value of the property (a delivery date) and has to split it and to put the result in the converter properties (year, month and day).
Reference to aggregate mapping
A reference to an aggregate contains data that in the relational model are stored in the same table as the main entity. For example, if you have an aggregate Address associated to a Customer, the address data is stored in the same data table as the customer data. How can you map this case with OpenXava?Let's see. In the model you can have:
Simply a reference to an aggregate. And for mapping it you can do:
You see how the aggregate members are mapped within the entity mapping that contains it. The only thing that you have to do is using the name of the reference as a prefix with an underline (in this case address_). You can observe that in the case of aggregates you can map references, properties and that you can use converters in the usual way.
Aggregate used in collection mapping
In case that you have a collection of aggregates, for example the invoice details, obviously the detail data is stored in a different table as the heading data. In this case the aggregate must have its own mapping. Let's see the example:Here the model part of Invoice:
You can see a collection of InvoiceDetail which is an aggregate. InvoiceDetail has to be mapped this way:
The aggregate mapping must be below of the main entity mapping. A component must have as many aggregate mappings as aggregates used in collections. The aggregate mapping has the same possibilities than entity mapping, with the exception that it's required to map a reference to the container object although maybe this reference is not defined in model. That is, although you do not define a reference to Invoice in InvoiceDetail OpenXava adds it automatically and you must map it (1).
Converters by default
You have seen how to declare a converter in a property mapping. But what happens, if you do not declare a converter? In reality in OpenXava all properties (except the key properties) have a converter by default. The default converters are defined in OpenXava/xava/default-converters.xml, that has a content like this:If you use a property of a type that is not defined here, by default OpenXava will assign the converter NoConversionConverter, a silly converter that don't perform anything.
In the case of key properties and references no converter are assigned; applying a converter to key properties can be problematic in certain circumstances, but even if you want to perform a conversion you can declare a converter explicitly in your mapping.
If you wish to modify the behavior of default converters in your application, you do not modify the OpenXava file, but you create your own converters.xml file in the folder xava of your project. You can assign a converter by default to a stereotype (using <for-stereotype/>).
Default mapping (new in v2.1.3)
Since version 2.1.3 OpenXava allows to define components without mapping, and it assumed a default mapping for it. For example, you can write:This component is mapped to the table Pupil, and the properties number and name are mapped to the columns number and name. The reference teacher is mapped to a column named teacher_number (if the key property of Teacher is named number).
Object/relational philosophy
OpenXava has been born and has been developed in an environment when it was necessary to work with legacy database without changing its structure. The result is that OpenXava:- Provides great flexibility when mapping with legacy database.
- Does not provide some features natural for OOT and that requires to change database scheme, as inheritance support or polymorphic queries.
Another cool feature of OpenXava mapping is that applications are 100% portables from JBoss CMP2 to Websphere CMP2 without writing a single line of code. Furthermore, the portability between Hibernate, JPA and EJB2 version of an application is very high, the mapping and all automatic controllers are 100% portable, obviously the custom EJB2, JPA or Hibernate code is not so portable.